26章 コードチューニングテクニック
26.3.4 qst_exe.icon
overview
25章のコードチューニング戦略をもとに具体的なテクニックの話
コードの速度改善とサイズを小さくする方法を紹介
コードチューニングは「アンチリファクタリング」であり、パフォーマンスと引き換えに内部構造を改悪する
ケースバイケース qst_exe.icon
コードチューニングにおける「経験則」は、効果測定以外は原則信用できない
ロジック部分以外は、保守性悪くなりそうqst_exe.icon
前にゲーム作ってたときに読んだやつ(Lua製のCorona)qst_exe.icon
26.1 ロジック
26.1.1 答えが分かったところで評価をやめる
短絡評価を使う言語しか触ったことがないので、なるほどという感じ
26.1.2 頻度順の評価
真になる可能性が高く、計算結果が早いものから実施するようにする
これを行うと頻度の高いケースがすぐに実行されるので、全体の計算数も少なくなる
サンプルでは、C#とJavaではこのチューニングの効果がなかったので、やはり効果測定が大事(VBでは効果あり)
26.1.3 同様の論理構造の比較
26.1.2のif-elseif-else版
こちらも効果測定ありき
26.1.4 複雑な式をテーブル参照で置き換える
テーブル定義の方がパフォーマンスがよくなった
コメントをつける等すれば、複雑な式でロジックを書くよりもわかりやすく改修もしやすい
26.1.5 遅延評価の使用
その処理は今する必要があるのかどうか
実際の仕事でもよくあるqst_exe.icon
26.2 ループ
26.2.1 ループ外分岐
条件式をループの中に入れずに、条件の中にループを入れる
条件式は意思決定であり、ループのたびに意思決定を入れないようにしたいとのこと
本にもあるように分かりにくいし、保守もしにくそうqst_exe.icon
26.2.2 ジャミング
2つのループを1つにして全体のループ数を減らす
インデックスが変わる系の処理には使えない
26.2.3 展開
ループ数をへらす処理
サンプルを見たけど、保守性がかなり悪そう
Pythonにいたってはパフォーマンスが悪化していた
26.2.4 ループ内処理の最小化
ループ外でもできる処理はループ外で実行することで、ループ内での処理をへらす
26.2.5 sentinel値
ループ内で評価を単純にするためにsentinel(番兵)値を利用する
26.2.6 最もビジーなループを内側に
内側にループ数が多いものを持ってくることで、総ループ数を減らす
26.2.7 強度の削減
加算よりも乗算の方が高価
かなり読みにくいし、間違えそうqst_exe.icon
26.3 データ変換
26.3.1 浮動小数点数から整数への変更
それはそう
26.3.2 配列の次元数の削減
多次元配列よりは1次元配列の方がパフォーマンスがよい
26.3.3 配列参照の削減
ループで配列の要素を利用してる場合は、参照を切って代入に変えるだけでよい
ループ内処理の最小化派生かな
26.3.4 補助的なインデックス
文字列の場合には、文字列の長さをデータとして持たせておく
独立した並列インデックス構造は???だった qst_exe.icon
26.3.5 moch5oMaki.icon
26.3.5 キャッシュの使用
計算結果をプログラムにキャッシュするというの、見たことなかったけどなるほどなという感じ。
本書では、高価な計算を省略する、という意図でキャッシュを利用しているが、
ルーチンの新しいバージョンは、最初のバージョンよりも複雜で、行数も増えている。したがって、それを帳消しにするほどの速度でなければ割に合わない。
とあるように、計測してパフォーマンスが出るという確証をもって使わないといけない(だし、パフォーマンス向上なら、もっと他にできることありそう)
26.4 式
26.4.1 代数的な恒等式の活用
論理演算については個人的にはパフォーマンスよりわかり易さのほうが大事かなと思ったりmoch5oMaki.icon
この例はシンプルだけど、複雑になると…
とはいえここまで凝ったことやるシーンって通常のWebアプリケーションではあまりなさそう
sqrtの例はそれはそうだねっていう。
26.4.2強度の削減の使用
コードの中で指数計算を使うことがなくて意識してなかったけど、たしかにーという感じ。
シフト演算も言われてみればそうだけど、実コードで見たことないですね。よく使う手なのかなmoch5oMaki.icon
乗算を加算に置き換える
指数関数を乗算に置き換える
型を適切に選択する(double とか floatの選択)
整数の2による乗除算をシフト演算に置き換える
色々テクニックが書いてあるけど、理論が実践に遠く及ばないいい例 とあるようにパフォーマンスに寄与しないケースももちろんあるので注意が必要とのこと。
26.4.3 コンパイル時の初期化
毎回行う必要がある計算に対して、計算済みの結果を使う例
26.4.4 システムルーチンへの警戒
各言語の持ってるルーチンに対して警戒しましょうという話。たとえば必要以上の制度で計算している可能性があるとのこと。
超越関数(ちょうえつかんすう、英: transcendental function)とは、多項式方程式を満たさない解析関数であり、代数関数と対照的である。言い換えると、超越関数は加算、乗算そして冪根という代数的演算を有限回用いて表せないという意味で代数を「超越」したものである。
これも個人的にコードの中で使ったことないのであまりピンと来なかったけど、人間から見ても大変そうな演算なので頭の片隅にとめておくといいのかもしれないmoch5oMaki.icon
26.4.6 計算済みの結果
ループのところで出てきた話、強度の削減などの項目とだいたいかぶってるので割愛
26.4.7 共通の部分式の削除
これはだいたいやってるんじゃないかなぁ。DRYですね。
26.5 ルーチン
コードチューニングにおいて最も強力なツールの1つは、ルーチンを適切に分解することである。
わかるmoch5oMaki.icon
小さなルーチンは低水準言語に書き直すのが比較的簡単である。
......moch5oMaki.icon
26.5.1 ルーチンのインライン化
インライン化することでパフォーマンス向上することがあるということらしいけど、普段は読みやすさ優先でいいと思う
26.6 低水準言語への置き換え
これは自分ではできそうにないけど、こういう世界もあるのだなぁということで読みました。
C++でパフォーマンスでなかったらアセンブラで書くってどんだけ、と思ってしまったmoch5oMaki.icon
時代的なものもあると思うけど、コンパイラや言語の開発者の皆さんの努力に感謝するしかないなと思いました。
コードチューニングテクニック
速度とサイズの改善、速度のみの改善についてそれぞれチェックリストがあるけど割愛
今のところは「こういう手がある」というのを持っているだけで良さそうな感じ。
26.7 物事は変わってように見えても、実は変わっていない
筆者自身が初版の発行時と比べてパフォーマンスが問題になるケースが当時と変わっているということについて述べている。
本章で説明したレベルのパフォーマンスの最適化は、一般的なプログラムの多くに当てはまらない
とも言っている。
一方で、組み込みシステムなどでパフォーマンスが求められるシーンではコードチューニングのたびに計測が必要で、「計測可能な改善」にこだわることが大事だと書いている。
なぜなら、パフォーマンスチューニングは複雑さ、読みやすさ、保守性などとトレードオフとなり重い負担を強いることがわかっているから。
これは読みながら考えていたこととだいたい一致していたので、そうだよねっていう感じmoch5oMaki.icon
26.8 参考資料
興味があるのがあれば教えて下さい。
26.9 まとめ
最適化の結果は言語、コンパイラ、環境の違いに大きく影響される。測定しなければそれがプログラムを改善するのか改悪するのかはわからない。
1つ目の最適化は最善のものでない場合が多い。良い最適化を見つけたあとも、他にもっと良い最適化がないか探し続ける。
コードチューニングは議論の分かれる感情的なテーマである。信頼性や保守性に極めて有害であるため。本書で紹介したテクニックを使うことにした場合はくれぐれも注意して適用するように。